
package com.x52im.rbchat.logic.chat_friend;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;

import com.eva.android.ArrayListObservable;
import com.eva.android.ObserverMain;
import com.eva.android.ToolKits;
import com.eva.android.widget.WidgetUtils;
import com.eva.android.widget.WidgetUtils.ToastType;
import com.eva.android.widget.alert.AlertDialog;
import com.x52im.rbchat.Const;
import com.x52im.rbchat.R;
import com.x52im.rainbowchat.http.logic.dto.RosterElementEntity;
import com.x52im.rainbowchat.im.dto.ChatType;
import com.x52im.rainbowchat.im.dto.MsgType;
import com.x52im.rbchat.logic.alarm.AlarmType;
import com.x52im.rbchat.logic.chat_friend.gift.GiftsToolsPopupWindow;
import com.x52im.rbchat.logic.chat_friend.impl.MoreUIWrapperX;
import com.x52im.rbchat.logic.chat_friend.utils.MessageForwardHelper;
import com.x52im.rbchat.logic.chat_friend.utils.ObserverFactory;
import com.x52im.rbchat.logic.chat_friend.utils.MessageHelper;
import com.x52im.rbchat.logic.chat_friend.utils.MessageReSendHelper;
import com.x52im.rbchat.logic.chat_friend.vv.IRealTimeVoiceWrapper;
import com.x52im.rbchat.logic.chat_friend.vv.RealTimeVoiceWrapper;
import com.x52im.rbchat.logic.chat_friend.vv.VoiceCallOutDialog;
import com.x52im.rbchat.logic.chat_root.AbstractChattingActivity;
import com.x52im.rbchat.logic.chat_root.AbstractChattingListAdapter;
import com.x52im.rbchat.logic.chat_root.model.VoipRecordMeta;
import com.x52im.rbchat.logic.chat_root.face.FaceBoardView;
import com.x52im.rbchat.logic.chat_root.impl.AbstractMoreUIWrapper;
import com.x52im.rbchat.logic.chat_root.impl.UnreadMessageBallonWrapper;
import com.x52im.rbchat.logic.chat_root.model.Message;
import com.x52im.rbchat.logic.chat_root.sendfile.SendFileHelper;
import com.x52im.rbchat.logic.chat_root.sendimg.SendImageWrapper;
import com.x52im.rbchat.logic.chat_root.sendvoice.SendVoiceDialog;
import com.x52im.rbchat.logic.chat_root.targetchoose.TargetSource;
import com.x52im.rbchat.logic.chat_root.targetchoose.TargetSourceFilterFactory;
import com.x52im.rbchat.network.http.bigfile.BigFileUploadManager;
import com.x52im.rbchat.permission2.PermissionManager;
import com.x52im.rbchat.utils.ActivityRequestCode;
import com.x52im.rbchat.utils.ActivityResultCode;
import com.x52im.rbchat.utils.BroadcastToolKits;
import com.x52im.rbchat.utils.IntentFactory;
import com.x52im.rbchat.utils.PreferencesToolkits;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Observable;
import java.util.Observer;

import androidx.core.app.ActivityCompat;

/**
 * 一对一聊天界面主Activity类.
 * <p>
 * 重要说明：虽本类继承自DataLoadableActivity类，为了满足自定能力没有使用默
 * 认自定标题，而是单独在layout文件里作了自已的标题layout，一定要注意！
 * 
 * @author Jack Jiang(http://www.52im.net/space-uid-1.html)
 * @version 1.0
 */
public class FriendChattingActivity extends AbstractChattingActivity
{
	private final static String TAG = FriendChattingActivity.class.getSimpleName();
	
	//=============================================================== 标准聊天相关
	/** 按钮：发送 */
	private Button btnSend;
	/** 按钮：打开输入框下方的"更多功能"面板  */
	private Button btnOpenPlusFunctions;
	/** 按钮：返回 */
	private Button btnBackToFriends = null;
	/** 按钮：更多。。。 */
	private Button btnSeeMore = null;
	/** 按钮：发送语音留言 */
	private Button btnSendVoice = null;
	/** 标签组件：消息输入 */
	private EditText txtMsg;
	/** 文本组件：好友昵称 */
	private TextView viewNickName;
	/** 图标组件：界面标题后面的静音图标 */
	private ImageView viewSilentIco;
	/** 文本组件：在线状态 */
	private TextView viewLiveStatus;
	
	/** 聊天消息列表 */
	private ListView listView;
	/** 聊天消息列表adapter */
	private FriendChattingListAdapter listAdapter;
    /** 当消息列表最后一行处于可见或不可见时的新消息（未读消息）提示ui包装实现类. */
    private UnreadMessageBallonWrapper unreadMessageBallonWrapper = null;
	
	/** 输入框左边功能：发送图片包装实现对象 */
	private SendImageWrapper menuWindowForSendPic = null;
	/** 输入框左边功能：发送语音留言对话框 */
	private SendVoiceDialog sendVoiceDialog = null;
	
	/** 聊天界面的根面板 */
	private LinearLayout layoutChattingRoot = null;

	/** FFF 表情按钮及面板 */
	private Button btnOpenFace;
	private FaceBoardView faceBoardView;

	// FIXME: 此变量因历史原因而存在，未来将逐步统一由父类中的toId替代之，本变量将在以后版本中弃用
	/**
	 * @deprecated by super.toId
	 * 暂存从Intent中传过来的好友信息数据（将要用于界面展现）. */
	private String friendUIDForInit = null;

	// 当从Notification的提示信息里点击进来时，用于聊天界面处理后台时的实时语音聊天请求的处理：
	// true表示打开ChatActivity时立即启动“接收”呼叫的Dialog，否则不启动
	private boolean startupRealTimeVoiceBeCallingForInit = false;
	// 当从Notification的提示信息里点击进来时，用于聊天界面处理后台时的实时语音聊天请求的处理：
	// 当startupRealTimeVoiceBeCallingForInit==true时表示收到对方请求时的时间戳（本机）
	private long startupRealTimeVoiceBeCallingTimeForInit = 0;
	
	/** 聊天消息列表ListViewAdapter的数据集合对象 */
	private ArrayListObservable<Message> chattingDatas = null;
    /** 数据模型变动观察者实现block */
    private ObserverMain chattingDatasObserver = null;
	
	/** 
	 * 好友的上线下观察者（此观察者通常用于上下线的情况下普通ui的通知的展现等）.
	 * <p>
	 * 目前本观察者用于好友列表界面上的实时刷新好友列表，用于显示该好友的在线状态等. */
	private final ObserverMain friendsLiveStatusChangeObs = createFriendsLiveStatusChangeObs();

	/**
	 * 广播监听者：用于接收修改完成好友的备注后的的广播（用于刷新ui，及时显示）*/
	private BroadcastReceiver friendRemarkChangedBR = null;

	//=============================================================== “更多”功能
	/** “更多“功能父布局 */
	private FrameLayout layoutbottomContent = null;
	/** "更多"功能gridview UI封装类 */
	private AbstractMoreUIWrapper moreUIWrapper = null;
	
	
	//=============================================================== 礼品相关
	/** 礼品工具包装类 */
	private GiftsToolsPopupWindow giftsToolsPopupWindow = null;
	/** 
	 * 收到好友发过来的礼品观察者对象.
	 * 目前此观察者只要用于聊天界面中在收到礼品后能即时刷新“我的礼品包”的ui展现之用. */
	private Observer receivedGiftObserverForCommonUI = new ObserverFactory.ReceivedGiftObserverForCommonUI(this);
	
	
	//=============================================================== 实时音视频相关 STAR
	private IRealTimeVoiceWrapper realTimeVoiceWrapper = null;
	/**
	 * 实时语音聊天请求观察者.
	 */
	private Observer realTimeVoiceChatRequestObserver = null;
	/**
	 * 视频聊天请求观察者.
	 */
	private final Observer viodeoChatRequestObserver = new ObserverFactory.ViodeoChatRequestObserver(this);
	//=============================================================== 实时音视频相关 END


	//=============================================================== 大文件发送相关 START
	/**
	 * 设置{@link BigFileUploadManager}中大文件任务状态改变观察者(主要用于“我”发送的大文件消息)，
	 * 用于UI及时刷新大文件上传状态在界面上的显示（本观察者通常由对应的UI界面设置，界面退到后台消失时取消设置） */
	private ObserverMain fileStatusChangedObserver = createFileStatusChangedObserver();
	//=============================================================== 大文件发送相关 END


	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.chatType = ChatType.CHAT_TYPE_FREIDN$CHAT;
		super.onCreate(savedInstanceState);

		//************************************************************ 礼品相关代码 START
		imc().getTransDataListener().setReceivedGiftObserverForCommonUI(receivedGiftObserverForCommonUI);
		if(giftsToolsPopupWindow != null)
			giftsToolsPopupWindow.forParentResume();
		//************************************************************ 礼品相关代码 END
	}

	/**
	 * 根据Activity的生命周期：本Activity每次从后台回到前台时调用本方法。
	 */
	protected void onResume() {
		super.onResume();

		// 及时根据群信息里设置的"消息免打扰"，刷新ui显示
		this.viewSilentIco.setVisibility(!PreferencesToolkits.isChatMsgToneOpen(this, friendUIDForInit)?View.VISIBLE:View.GONE);

		// 设置当前正处于激话状态下的聊天好友uid
		imc().setCurrentFrontChattingUserUID(friendUIDForInit);
		
		// 设置实时视频聊天请求观察者：为何要使用观察者？理由与类{@link VideoActivity.BaseEventForVideoChatObserver}相似。
		imc().getTransDataListener().setViodeoChatRequestObserver(viodeoChatRequestObserver);
		// 设置实时语音聊天请求观察者：为何要使用观察者？理由与类{@link VideoActivity.BaseEventForVideoChatObserver}相似。
		imc().getTransDataListener().setRealTimeVoiceChatRequestObserver(realTimeVoiceChatRequestObserver);
		// 设置好友上下线状态观察者
		RosterElementEntity.addLiveStatusChangeObs(friendsLiveStatusChangeObs);
		// 设置大文件上传状态变更观察者(主要用于“我”发送的大文件消息)
		imc().getBigFileUploadManager().setFileStatusChangedObserver(fileStatusChangedObserver);
		// APP中唯一重置未读正式聊天消息的代码：最后确保重置APP首页“消息”未读消息数字的显示
		imc().getAlarmsProvider().resetFlagNum(AlarmType.friendChat, friendUIDForInit, 0, true);
		
		// 更积极地刷新在线状态显示，确保状态显示的准确性（从如从后台回到前台时）
		refreshLiveStatusUIAuto();
	}
	
	/**
	 * 根据Activity的生命周期：本Activity每次从前台退到后台时调用本方法。
	 */
	protected void onPause() {
		super.onPause();
		
		// 取消设置当前正处于激话状态下的聊天好友uid
		imc().setCurrentFrontChattingUserUID(null);
		
		// 取消实时视频聊天请求观察者：为何要使用观察者？理由与类{@link VideoActivity.BaseEventForVideoChatObserver}相似。
		imc().getTransDataListener().setViodeoChatRequestObserver(null);
		// 取消实时语音聊天请求观察者：为何要使用观察者？理由与类{@link VideoActivity.BaseEventForVideoChatObserver}相似。
		imc().getTransDataListener().setRealTimeVoiceChatRequestObserver(null);
		// 取消设置大文件上传状态变更观察者(主要用于“我”发送的大文件消息)
		imc().getBigFileUploadManager().setFileStatusChangedObserver(null);
		
		// 如果真在进行语音留言录制的话就hide它
		if(sendVoiceDialog != null)
			sendVoiceDialog.hide();
	}
	
	/**
	 * 本activity被销毁时额外要做一些事.
	 */
	@Override
	protected void onDestroy() {
		//************************************************************ 实时语音代码 START
		realTimeVoiceWrapper.stopNoConfirm(true);
		//************************************************************ 实时语音代码 END
		
		// 如果真在进行语音留言录制的话就销毁它
		if(sendVoiceDialog != null)
			sendVoiceDialog.dismiss();
		// 释放ListAdapter中需要释放的资源
		if(listAdapter != null)
			listAdapter.forParentDestraoy();
		
		// 逆初始化：释放资源
		deInitToFriend(friendUIDForInit);
		
		//************************************************************ 礼品相关代码 START
		imc().getTransDataListener().setReceivedGiftObserverForCommonUI(null);
		if(giftsToolsPopupWindow != null)
			giftsToolsPopupWindow.forParentPause();
		//************************************************************ 礼品相关代码 END

		// 取消注册广播监听者：修改完成好友的备注后的广播
		BroadcastToolKits.friendRemarkChanged_UNREGISTER(this, friendRemarkChangedBR);

		super.onDestroy();
	}
	
	//************************************************************ 实时语音代码 START
	/**
	 * 实现当正在语音聊天时能提示用户是否关闭.
	 */
	@Override
	public void finish() {
		if(realTimeVoiceWrapper != null && realTimeVoiceWrapper.isTalking()) {
			new AlertDialog.Builder(FriendChattingActivity.this)
				.setTitle(R.string.general_prompt)
				.setMessage(FriendChattingActivity.this.getString(R.string.real_time_chat_end_chatting))
				.setPositiveButton(R.string.general_yes, (dialog, which) -> {realTimeVoiceWrapper.stopNoConfirm(true);FriendChattingActivity.super.finish();})
				.setNegativeButton(FriendChattingActivity.this.getString(R.string.general_no), null)
				.show();
		}
		else
			super.finish();
	}
	//************************************************************ 实时语音代码 END
		
	/**
	 * 从Intent中读取传递过来的数据。
	 */
	@Override
	protected void initDataFromIntent() {
		super.initDataFromIntent();

		//解析从intent中传过来的数据
		ArrayList intentDatas = IntentFactory.parseChatIntent(getIntent());
		friendUIDForInit = (String)intentDatas.get(0);
		startupRealTimeVoiceBeCallingForInit = (Boolean)intentDatas.get(1);
		startupRealTimeVoiceBeCallingTimeForInit = (Long)intentDatas.get(2);

		super.toId = this.friendUIDForInit;
		super.toName = null;
	}
	
	/**
	 * 本方法由父类的onCreate()调用，子类可在此方法中实现自已的UI显示逻辑。
	 */
	@Override
	protected void initViews(Bundle savedInstanceState) {
		super.initViews(savedInstanceState);

		// 养成良好习惯：首先设置主layout，确保后绪的操作中使用到的组件都可以被find到
		setContentView(R.layout.chatting_list_view_friend);

		//** 【2】各功能组件引用
		txtMsg = (EditText)this.findViewById(R.id.chatting_list_view_msgEdit);
		btnSendVoice = (Button)this.findViewById(R.id.chatting_list_view_voiceRecordBtn);
		btnSend = (Button)this.findViewById(R.id.chatting_list_view_sendBtn);
		btnOpenPlusFunctions = (Button)this.findViewById(R.id.chatting_list_view_plusBtn);
		viewNickName = (TextView)this.findViewById(R.id.chatting_list_view_nickNameView);
		viewSilentIco = (ImageView) this.findViewById(R.id.chatting_list_view_silentIconView);
		viewLiveStatus = (TextView)this.findViewById(R.id.chatting_list_view_liveStatusView);
		btnBackToFriends = (Button)this.findViewById(R.id.chatting_list_view_backToFriendsBtn);
		btnSeeMore = (Button)this.findViewById(R.id.chatting_list_view_seeMoreBtn);
		// 聊天界面的根面板
		layoutChattingRoot = (LinearLayout)this.findViewById(R.id.chatting_list_view_rootLL);
		// 输入框下方的功能区父布局
		layoutbottomContent = (FrameLayout)this.findViewById(R.id.chatting_list_view_bottomContentFL);
		
		//** 【3】消息列表及列表的adapter
		listView = (ListView) findViewById(R.id.chatting_list_view_listView);
		// 初始化消息列表及列表的adapter
		initListViewAndAdapter();
		
		//** 【6】根据Intent中传过来的好友信息数据，切换到与该好友的聊天
		this.initToFriend();
		
		//** 【7】实时语音代码
		//************************************************************  实时语音代码 START
		realTimeVoiceWrapper = new RealTimeVoiceWrapper(this);
		realTimeVoiceChatRequestObserver = new ObserverFactory.RealTimeVoiceChatRequestObserver(this
				, realTimeVoiceWrapper.getLayoutOfRealTimeVoiceOpr(), realTimeVoiceWrapper);
		
		// ## 当从Notification的提示信息里点击进来时，用于聊天界面处理后台时的实时语音聊天请求的处理
		// ## ，目的是希望用户能点击进来就自动打开请求处理界面，从而提升用户体验（try to startup real time voice be calling）
		if(startupRealTimeVoiceBeCallingForInit) {
			// 实时语音请求超时了（多长时间算超时呢？默认的呼叫超时时间-4秒钟的结果，目
			// 的就是希望这边点开的时候能与呼叫方在时间点上保持一致，4秒的时间差是假设的指令传输延迟）
			if(System.currentTimeMillis() - startupRealTimeVoiceBeCallingTimeForInit > (VoiceCallOutDialog.DEFAULT_CALLING_TIMEOUT - 4 * 1000)) {
				new AlertDialog.Builder(self())
					.setTitle($$(R.string.general_prompt))  
					.setMessage($$(R.string.real_time_chat_be_request_from_notification_timeout_hint))
					.setPositiveButton($$(R.string.real_time_chat_be_request_from_notification_timeout_hint_btn), (dialog, which) -> sendPlainTextMessageImpl($$(R.string.real_time_chat_be_request_from_notification_timeout_hint_message), false, null))
					.setNegativeButton($$(R.string.general_cancel), null)
					.show();
			} else {
				if(realTimeVoiceChatRequestObserver != null)
					realTimeVoiceChatRequestObserver.update(null, friendUIDForInit);
			}
		}
		//************************************************************  实时语音代码 END
		
		//** 【8】实时语音代码实例化发送图片包装实现类
		menuWindowForSendPic = new SendImageWrapper(this, layoutChattingRoot, friendUIDForInit, Const.CHAT_TYPE_FREIDN$CHAT);

		//** 【10】输入框下方的更多功能
		this.initChatFunctionsUI();

		// ** FFF 表情按钮及面板
		this.initFaceUI();
	}
	
	/**
	 * 本方法由父类的onCreate()调用，子类可在此方法中为各UI功能组件增加事件临听。
	 */
	protected void initListeners() {
		//** 为以下组件添加点击查看好友信息的事件处理(进入聊天信息界面)
		OnClickListener lisToSeeFriendInfo = v -> {gotoChatInfoActivity(self(), friendUIDForInit, viewNickName.getText().toString());};
		viewLiveStatus.setOnClickListener(lisToSeeFriendInfo);
		viewNickName.setOnClickListener(lisToSeeFriendInfo);
		btnSeeMore.setOnClickListener(lisToSeeFriendInfo);
		
		//**　文本消息发送按钮事件处理
		btnSend.setOnClickListener(v -> doSendTextMessage());
		
		//** 标题栏的返回按钮事件处理
		btnBackToFriends.setOnClickListener(v -> FriendChattingActivity.this.finish());
		
		//** 消息输入框左边的语音留言按钮事件处理
		btnSendVoice.setOnClickListener(v -> {
			// 开始动态权限申请（用于targetSDKVersion>=23，即Android6.0及以上系统时）
			PermissionManager.requestPermission_RECORD_AUDIO(self()
					// 权限获取成功或已经取得此权限时
					, (o, arg) -> {

						//** 权限正常获取，走正常的代码逻辑。。。

						// 消息列表首先自动滚动到最后一行
						listAdapter.showLastItem();
						// 把句柄缓存起 ，以便 下次打开时能快一点（经实测，跟每次都new SendVoiceDialog，确实要快很多哦！）
						if(sendVoiceDialog == null)
							sendVoiceDialog = new SendVoiceDialog(FriendChattingActivity.this, friendUIDForInit, Const.CHAT_TYPE_FREIDN$CHAT);
						// 显示之
						sendVoiceDialog.show();
					}
					, null);
		});
	
		//** 初始化输入框下方的”更多“功能按钮、面板等事件处理逻辑
		initChatFunctionsLisnter();

		//** 长按弹出菜单事件注册和处理逻辑
		super.initListViewListenersImpl(this, listView, listAdapter);
	}
	
	/**
	 * 初始化消息列表及列表的adapter。
	 */
	private void initListViewAndAdapter() {
		// 生成适配器的ImageItem <====> 动态数组的元素，两者一一对应
		if(chattingDatas == null) {//先实例化列表的全局数据结构对对象（如果已经实例化了就不需要现新那对 象了，目的是为了保留上次的数据）
			chattingDatas = new ArrayListObservable<>();
		}

        // 初始化当消息列表最后一行处于可见或不可见时的新消息（未读消息）提示ui包装实现对象
        unreadMessageBallonWrapper = new UnreadMessageBallonWrapper(this, R.id.chatting_list_view_unreadBallonBtn){
            /**
             * 当点击未读气泡时要调用的方法.
             */
            @Override
            protected void fireOnClickBallon(){
                // 点击未读气泡时自动让消息列表滚动到最后一行
                if(listAdapter != null)
                    listAdapter.showLastItem();
            }
        };
		
		listAdapter = new FriendChattingListAdapter(this, listView, chatType, toId);
		listView.setAdapter(listAdapter);
	}

	/**
	 * 初始化表情功能。
	 */
	private void initFaceUI() {
		btnOpenFace = this.findViewById(R.id.chatting_list_view_openFaceBtn);
		faceBoardView = this.findViewById(R.id.i_faceboard);
		faceBoardView.setInputEt(txtMsg); // 绑定输入框

		// 表情面板的事件处理（出于通用目的，暂时只有OnOperationListener事件被提炼到父类中）
		super.initFaceListenersImpl(this, faceBoardView, txtMsg);

		// 表情面板显示后要做的事：消息列表滚动到底部
		faceBoardView.setOnToolBoxListener(new FaceBoardView.OnToolBoxListener() {
			@Override
			public void onShowFace() {
//				ToolKits.hideInputMethod(ChatActivity.this, txtMsg);
				moreUIWrapper.hide();
				listAdapter.showLastItem();
				// 按钮变为键盘模式
				btnOpenFace.setBackgroundResource(R.drawable.chat_keyboard_btn);
			}
			@Override
			public void onHideFace() {
				listAdapter.showLastItem();
				// 按钮变为表情模式
				btnOpenFace.setBackgroundResource(R.drawable.chat_face_btn);
			}
		});

		// FFF 打开表情面板
		btnOpenFace.setOnClickListener(faceBoardView.getFunctionBtnListener(1));
	}
	
	/**
	 * 初始化输入框下方的”更多“功能面板的UI
	 */
	private void initChatFunctionsUI() {
		// 实例化输入框下方的更多功能封装实现类
		this.moreUIWrapper = new MoreUIWrapperX(this, this.layoutbottomContent){
			
			/**
			 * 点击了gridview中的相应功能时将自动触发本方法。
			 * 
			 * @param actionId 相应功能的action id
			 */
			@Override
			protected void fireChatFunctionsAction(int actionId) {
				switch (actionId) {
					case MoreUIWrapperX.MORE_ACTION_ID_IMAGE:
						menuWindowForSendPic.doChoosePhoto();
						// 处理完点击事件后即可关闭更多功能布局
						this.hide();
						break;
					case MoreUIWrapperX.MORE_ACTION_ID_PHOTO:
						menuWindowForSendPic.doTakePhoto();
						// 处理完点击事件后即可关闭更多功能布局
						this.hide();
						break;
					case MoreUIWrapperX.MORE_ACTION_ID_REALTIME_VOICE_CHAT: {
						doRealtimeVoiceChat_checkPermision();
						// 处理完点击事件后即可关闭更多功能布局
						this.hide();
						break;
					}
					case MoreUIWrapperX.MORE_ACTION_ID_REALTIME_VIDEO_CHAT: {
						doRealtimeVideoChat_checkPermision();
						// 处理完点击事件后即可关闭更多功能布局
						this.hide();
						break;
					}
					case MoreUIWrapperX.MORE_ACTION_ID_GIFT:
						doShowGiftsToolsPopupWindow();
						this.hide(); // 处理完点击事件后即可关闭更多功能布局
						break;
					case MoreUIWrapperX.MORE_ACTION_ID_FILE:
						SendFileHelper.openFileChooser(FriendChattingActivity.this);
						// 处理完点击事件后即可关闭更多功能布局
						this.hide();
						break;
					case MoreUIWrapperX.MORE_ACTION_ID_SHORTVIDEO: {
						// 开始动态权限申请（用于targetSDKVersion>=23，即Android6.0及以上系统时）
						PermissionManager.requestPermission_CAMERA_$_RECORD_AUDIO(FriendChattingActivity.this
								// 权限获取成功或已经取得此权限时（走正常的代码逻辑），进入短视频录制界面
								, (o, arg) -> ActivityCompat.startActivityForResult(self(), IntentFactory.createShortVideoRecordActivityIntent(FriendChattingActivity.this)
										, ActivityRequestCode.SHORTVIDEO_RECORD_RESULT_FROM_ACTIVITY, null)
								, null);

						// 处理完点击事件后即可关闭更多功能布局
						this.hide();
						break;
					}
					case MoreUIWrapperX.MORE_ACTION_ID_CONTACT_FRIEND: {
//						Intent i = IntentFactory.createUserChooseActivityIntent(FriendChattingActivity.this
//								, TargetChooseActivity.USED_FOR_SEND_CONTACT_MESSAGE, Const.CHAT_TYPE_FREIDN$CHAT, friendUIDForInit);
						Intent i = IntentFactory.createTargetChooseActivityIntent(self()
								, TargetSource.friend, null, TargetSourceFilterFactory.createTargetSourceFilter4UserContact(chatType, toId), null, null, null, null);
						ActivityCompat.startActivityForResult(self(), i, ActivityRequestCode.REQUEST_CODE_FOR_CONTACT_TARGET_CHOOSE, null);

						// 处理完点击事件后即可关闭更多功能布局
						this.hide();
						break;
					}
					case MoreUIWrapperX.MORE_ACTION_ID_CONTACT_GROUP: {
						Intent i = IntentFactory.createTargetChooseActivityIntent(self(), TargetSource.group, null, null, null, null, null, null);
						ActivityCompat.startActivityForResult(self(), i, ActivityRequestCode.REQUEST_CODE_FOR_CONTACT_TARGET_CHOOSE, null);

						// 处理完点击事件后即可关闭更多功能布局
						this.hide();
						break;
					}
					case MoreUIWrapperX.MORE_ACTION_ID_LOCATION: {
						Intent intent = IntentFactory.createGetLocationActivityIntent(FriendChattingActivity.this);
						ActivityCompat.startActivityForResult(self(), intent, ActivityRequestCode.REQUEST_CODE_FOR_GETTION, null);
						// 处理完点击事件后即可关闭更多功能布局
						this.hide();
						break;
					}
				}
			}
		};
	}
	
	/**
	 * 初始化输入框下方的”更多“功能按钮、面板等事件处理逻辑.
	 */
	private void initChatFunctionsLisnter()
	{
		//* "+"号按钮事件处理
		btnOpenPlusFunctions.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				// 尝试关闭输入法（如果输入法已开启，无条件关闭之）
				ToolKits.hideInputMethod(self(), txtMsg);
				// 尝试关闭表情面板
				faceBoardView.hideLayout();

				// "+"号对应的内容面板当前未显示时（接下来应该被显示）
				if(!moreUIWrapper.isShowing()) {
					// 延迟一会，让键盘先隐藏再显示内容面板，否则会有一瞬间内容面板和软键盘同时显示的ui跳动
					layoutbottomContent.postDelayed(() -> {
						switchBottomContent();
					}, 50);
				} else {
					switchBottomContent();
				}
			}

			private void switchBottomContent() {
				// 打开或关闭更多功能面板
				moreUIWrapper.auto();
				// 消息列表首先自动滚动到最后一行
				listAdapter.showLastItem();
			}
		});

		//* 点击消息输入文本框时强制隐藏”更多“功能面板（如果它正在显示的话）
		txtMsg.setOnClickListener(v -> {
			// 消息列表首先自动滚动到最后一行
			listAdapter.showLastItem();
			// 关闭表情面板
			faceBoardView.hideLayout();
			// 当触发输入框时无条件关闭“更多”面板
			moreUIWrapper.hide();
		});
		
		//* 消息输入文本框获得焦点时隐藏”更多“功能面板（如果它正在显示的话）
		txtMsg.setOnFocusChangeListener((v, hasFocus) -> {
			if (hasFocus) {
				// 关闭表情面板
				faceBoardView.hideLayout();
				// 当输入框获点输入焦点时无条件关闭“更多”面板
				moreUIWrapper.hide();
			}
		});
		
		//* 消息输入文本框中有内容或无内容时隐藏或显示”更多“功能按针（如果它正在显示的话）
		txtMsg.addTextChangedListener(new TextWatcher() {             
			@Override    
			public void onTextChanged(CharSequence s, int start, int before, int count){    
				autoSwitchSendAndPlusBtn();
			}    

			@Override    
			public void beforeTextChanged(CharSequence s, int start, int count,int after) {     
				autoSwitchSendAndPlusBtn();
			}    

			@Override    
			public void afterTextChanged(Editable s) {     
				autoSwitchSendAndPlusBtn();
			}   

			// 如果消息文本框中有文本时设置消息”发送“按钮可见、”更多“功能按钮不可见，否则相反
			private void autoSwitchSendAndPlusBtn() {
				String txt = txtMsg.getText().toString();

				if(txt != null && txt.length() > 0) {
					btnSend.setVisibility(View.VISIBLE);
					btnOpenPlusFunctions.setVisibility(View.GONE);
				} else {
					btnSend.setVisibility(View.GONE);
					btnOpenPlusFunctions.setVisibility(View.VISIBLE);
				}
			}
		});  
		
		//*聊天列表listview的触摸事件(当上下滑动消息列表时)：
		// 1) 无条件关闭“更多”面板，就像微信一样；
		// 2) 如果输入法已开启，无条件关闭之，就像微信一样。
		listView.setOnTouchListener((v, event) -> {
			switch (event.getAction()) {
				case MotionEvent.ACTION_DOWN:
				case MotionEvent.ACTION_UP:
				case MotionEvent.ACTION_MOVE: {
					// 如果输入法已开启，无条件关闭之，就像微信一样
					ToolKits.hideInputMethod(FriendChattingActivity.this, txtMsg);
					// 当上下滑动消息列表时无条件关闭“更多”面板，就像微信一样
					moreUIWrapper.hide();
					// 关闭表情面板
					faceBoardView.hideLayout();
					break;
				}
			}
			return false;
		});
	}

	/**
	 * 初始化各广播监听器。
	 */
	@Override
	protected void initBroadCastReciever() {
		super.initBroadCastReciever();

		friendRemarkChangedBR = new BroadcastReceiver() {
			@Override
			public void onReceive(Context context, Intent intent) {
				String friendUid = intent.getStringExtra("friend_uid");
				String friendNicknameWithRemark = intent.getStringExtra("friend_nickname_with_remark");
				Log.i(TAG, "【好友备注更新】收到 (friendUid="+friendUid+"，friendNicknameWithRemark="+friendNicknameWithRemark+") 已修改完成的广播通知！");

				if(friendUid != null && friendUid.equals(friendUIDForInit)) {
					// 更新聊天界面上的标题栏
					updateTitle(friendNicknameWithRemark);
					Log.i(TAG, "【好友备注更新】当前聊天界面标题更新成功！");
				}
			}
		};
		// 注册广播监听者：用于接收修改完成好友的备注后的广播通知
		BroadcastToolKits.friendRemarkChanged_REGISTER(this, friendRemarkChangedBR);
	}
	
	/** 
	 * 新建一个好友的上线下观察者（此观察者通常用于上下线的情况下普通ui的通知的展现等）.
	 * <p>
	 * 目前本观察者用于好友列表界面上的实时刷新好友列表，用于显示该好友的在线状态等.
	 */
	private ObserverMain createFriendsLiveStatusChangeObs() {
		return new ObserverFactory.FriendLiveStatusChangeObs(){
			@Override
			protected void fireOnline(String nickName, String friendUid) {
				refreshLiveStatusUI(friendUid, true);
//				Log.i(TAG, MessageFormat.format($$(R.string.chat_friend_on_line), nickName));
			}
			@Override
			protected void fireOffline(String nickName, String friendUid) {
				refreshLiveStatusUI(friendUid, false);
//				Log.d(TAG, MessageFormat.format($$(R.string.chat_friend_off_line), nickName));
			}
		};
	}

	/**
	 * 大文件上传任务状态改变观察者.
	 *
	 * @return
	 */
	private ObserverMain createFileStatusChangedObserver() {
		return new ObserverMain(){
			@Override
			public void updateInMain(Observable observable, Object data) {
				// 收到状态改变通知就直接刷新消息列表UI
				listAdapter.notifyDataSetChanged();
			}
		};
	}

	/**
	 * 发起实时语音聊天（本方法内将自动检查并申请音频权限）。
	 */
	private void doRealtimeVoiceChat_checkPermision(){
		// 开始动态权限申请（用于targetSDKVersion>=23，即Android6.0及以上系统时）
		PermissionManager.requestPermission_CAMERA_$_RECORD_AUDIO(FriendChattingActivity.this
				// 权限获取成功或已经取得此权限时（走正常的代码逻辑）
				, (o, arg) -> doRealtimeVoiceChat()
				, null);
	}
	
	/**
	 * 发起实时语音聊天。
	 *
	 * 注意：如果APP的targetSDKVersion>=23时，调用本方法前，请确保已申请好相关权限！
	 */
	private void doRealtimeVoiceChat() {
		final RosterElementEntity friendRee = imc().getFriendsListProvider().getFriendInfoByUid2(friendUIDForInit);
		// 如果正在进行实时语音聊天则不允许开始实时语音聊天请求
		if (realTimeVoiceWrapper != null && realTimeVoiceWrapper.isTalking()) {
			new AlertDialog.Builder(self())
					.setTitle($$(R.string.general_faild))
					.setMessage($$(R.string.real_time_chat_request_at_real_time_voice_chating_for_source))
					.setPositiveButton($$(R.string.general_got_it), null)
					.show();
			return;
		}

		//** 自v10.0开始，已取消在在线状态在判断和要求！
		if (friendRee != null) {
//				&& friendRee.isOnline()) {
			// 实时音频聊天呼出
			new VoiceCallOutDialog(self(), friendUIDForInit) {
				@Override
				protected void fireEndExtra() {
//					showIneractInfo("You has been end time voice request.");
//					WidgetUtils.showToast(FriendChattingActivity.this, "You has been end time voice request.", ToastType.INFO);
				}

				/**
				 * 对方同意后要做的事
				 */
				@Override
				protected void fireWhenFriendAccept() {
					// 开始语音聊天
					realTimeVoiceWrapper.start(true, friendUIDForInit, -1);
				}

			}.show();
		} else {
//			WidgetUtils.showToast(self(), MessageFormat.format($$(R.string.general_lover_offline), String.valueOf(viewNickName.getText())), ToastType.WARN);
			WidgetUtils.showToast(self(), $$(R.string.general_invalid_data), ToastType.WARN);
		}
	}

	/**
	 * 发起实时视频聊天（本方法内将自动检查并申请音频和视频的权限）。
	 */
	private void doRealtimeVideoChat_checkPermision(){
		// 开始动态权限申请（用于targetSDKVersion>=23，即Android6.0及以上系统时）
		PermissionManager.requestPermission_CAMERA_$_RECORD_AUDIO(FriendChattingActivity.this
				// 权限获取成功或已经取得此权限时（走正常的代码逻辑）
				, (o, arg) -> doRealtimeVideoChat()
				, null);
	}
	
	/**
	 * 发起实时视频聊天。
	 *
	 * 注意：如果APP的targetSDKVersion>=23时，调用本方法前，请确保已申请好相关权限！
	 */
	private void doRealtimeVideoChat() {
		// 如果正在进行实时语音聊天则不允许开始实时语音聊天请求
		if(realTimeVoiceWrapper != null && realTimeVoiceWrapper.isTalking()) {
			new AlertDialog.Builder(self())
				.setTitle($$(R.string.general_faild))
				.setMessage($$(R.string.real_time_chat_request_at_real_time_voice_chating_for_source))
				.setPositiveButton($$(R.string.general_got_it), null) 
				.show(); 
			return;
		}

		gotoRealtimeVideoChatCall(this, friendUIDForInit, String.valueOf(viewNickName.getText()));
	}
	
	/**
	 * 发起文字聊天（调用本方法，会将文本框中输入的文字发送给对方）。
	 */
	private void doSendTextMessage() {
		sendPlainTextMessageImpl((o, arg) -> {
			txtMsg.setText("");
			if(quote4InputWrapper != null) {
				quote4InputWrapper.cancelQuote();
			}
		});
	}
	
	//************************************************************  礼品相关代码 START
	public void doShowGiftsToolsPopupWindow() {
		//实例化SelectPicPopupWindow
		if(giftsToolsPopupWindow == null) {
			giftsToolsPopupWindow = new GiftsToolsPopupWindow(false, this, friendUIDForInit);
			// 手动调用一下此方法，因giftsToolsPopupWindow只在使用时被初始化
			// ，所以很可能已错过了onResume的设置，所以此处要显式调用一下
			giftsToolsPopupWindow.forParentResume();
			giftsToolsPopupWindow.loadGiftsData();
		}
		//显示窗口
		giftsToolsPopupWindow.showAtLocation(btnOpenPlusFunctions, Gravity.BOTTOM|Gravity.CENTER_HORIZONTAL, 0, 0); //设置layout在PopupWindow中显示的位置
	}
	//************************************************************  礼品相关代码 END
	
	/**
	 * 默认将聊天文本框中的内容作为发送内分给好友.
	 */
	protected void sendPlainTextMessageImpl(Observer sucessObs) {
		sendPlainTextMessageImpl(txtMsg.getText().toString(), true, sucessObs);
	}

	public void sendPlainTextMessageImpl(String message, boolean includeQuote, Observer sucessObs) {
        MessageHelper.sendPlainTextMessageAsync(this
				, toId
				, message
				, includeQuote ? (quote4InputWrapper !=  null?quote4InputWrapper.getQuoteMeta(chatType, toId):null) : null
				, sucessObs);
	}
	
	/**
	 * 切换/初始化与该好友的聊天数据.
	 */
	private void initToFriend() {
		RosterElementEntity friendRee = imc().getFriendsListProvider().getFriendInfoByUid2(friendUIDForInit);
		// 为Null则有可能是这种情况：是从Notification点进来的，但该人员已经不是好友了（已删除）
		if(friendRee == null) {
			new AlertDialog.Builder(self())
				.setTitle($$(R.string.general_prompt))  
				.setMessage(ToolKits.i18n(this, R.string.chat_has_been_not_lover_hint, friendUIDForInit))
				.setPositiveButton($$(R.string.general_got_it), (dialog, which) -> finish())
				.show();
		} else {
			// 显示昵称
			updateTitle(friendRee.getNickNameWithRemark());
			// 刷新在线状态显示
			refreshLiveStatusUI(friendUIDForInit, friendRee.isOnline());

			// 当用户收到新消息时是通过通知观察者来刷新聊天界面中的ui显示的（从而保证显示最新消息）
			chattingDatasObserver = createChattingDatasObserver();
			// 切换到该用户的聊天记录
			chattingDatas = imc().getMessagesProvider().getMessages(this, friendRee.getUser_uid());
			// 设置观察者（参见：com.eva.android.ArrayListObservable API文档！）
            chattingDatas.addObserver(chattingDatasObserver);

			// 设置聊天列表的数据模型对象
			listAdapter.setListData(chattingDatas.getDataList());
			// 刷新列表ui显示
			listAdapter.notifyDataSetChanged();

			// true表示高亮成功（高亮当前用于从搜索功能中进入到聊天界面中，从而让搜索到的消息高亮显示并自将将该条滚动到列表可视区）
			boolean highlightOnceMessage = doHighlightOnceMessage(listView);
			// 当高亮不成功时，就自动滚到最后一行
			if(!highlightOnceMessage) {
				// 自动滚动到最后一行
				listAdapter.showLastItem();
			}
		}
	}

	/**
	 * 创建收到新消息的观察者。
	 * <p>
	 * 本观察者的作用是：用于当收到用户消息时，能由消息监督听者通知此观察者来完成ui的刷新（这样就能保证最新消息能即时显示在界面上了）。
	 *
	 * @return 观察者对象
	 */
	private ObserverMain createChattingDatasObserver(){
		return new ObserverMain() {
			@Override
			protected void updateInMain(Observable observable, Object data) {
				// 刷新ListView的UI显示
				listAdapter.notifyDataSetChanged();

				if (data != null) {
					ArrayListObservable.UpdateDataToObserver<Message> udto = (ArrayListObservable.UpdateDataToObserver<Message>) data;

					// 添加到聊天界面中的是"我"自已发出的消息
					if (udto.getExtraData() != null && udto.getExtraData().isOutgoing()) {
						// 自动让消息列表滚动到最后一行（不然会挡住自已新发的消息，影响体验哦）
						if (listAdapter != null)
							listAdapter.showLastItem();
					} else {
						// 【消息列表最后一条不可见时的处理1】当消息列表的最后不处于可见状态时（通常此时是由用户滑道列表上的面看历史消息了）
						if (!listAdapter.isLastItemVisible()) {
							if (udto != null && udto.getUpdateType() == ArrayListObservable.UpdateTypeToObserver.add) {
								// 新消息作为未读消息提示用户（有几条新消息未读——即有几条未读的正处于末尾的不可见状态）
								if (unreadMessageBallonWrapper != null)
									unreadMessageBallonWrapper.addUnreadCount(1);
							}
						}
						// 最后一条可见时，则无条件保证自动滚动到最后一行
						else {
							if (listAdapter != null)
								listAdapter.showLastItem();
						}
					}
				}
			}
		};
	}
	
	/**
	 * 逆初始化与该好友的聊天相关设置.
	 * <p>
	 * 本方法在资源回收时调用，是方法 {@link #initToFriend}的逆方法.
	 * 
	 * @param friendUIDForInit
	 */
	private void deInitToFriend(String friendUIDForInit) {
        // 切换到该用户的聊天记录
        chattingDatas = imc().getMessagesProvider().getMessages(this, friendUIDForInit);
		// 取消数据观察者
        if (chattingDatas != null && chattingDatasObserver != null) {
			chattingDatas.removeObserver(chattingDatasObserver);
        }

		// 清除好友上下线状态观察者
		RosterElementEntity.removeLiveStatusChangeObs(this.friendsLiveStatusChangeObs);
    }

	/**
	 * 刷新标题栏的显示。
	 *
	 * @param nicknameWithRemark
	 */
	private void updateTitle(String nicknameWithRemark) {
		toName = nicknameWithRemark;
		// 显示昵称
		this.viewNickName.setText(toName);
	}
	
	/**
	 * 刷新标题栏上用户的在线状态标识UI.
	 */
	private void refreshLiveStatusUIAuto() {
		RosterElementEntity friendRee = imc().getFriendsListProvider().getFriendInfoByUid2(friendUIDForInit);
		// 为Null则有可能是这种情况：是从Notification点进来的，但该人员已经不是好友了（已删除）
		if(friendRee != null)
			refreshLiveStatusUI(friendUIDForInit, friendRee.isOnline());
		else
			refreshLiveStatusUI(friendUIDForInit, false);
	}
	
	/**
	 * 刷新标题栏上用户的在线状态标识UI.
	 * 
	 * @param online
	 */
	private void refreshLiveStatusUI(String uid, boolean online) {
		if(uid != null && uid.equals(friendUIDForInit)) {
			if (online) {
				viewLiveStatus.setText(R.string.chat_activity_livestatus_onlie_desc);
				WidgetUtils.setDrawLeft(self(), R.drawable.sns_friend_list_form_item_status_online_ico2, viewLiveStatus);
			} else {
				viewLiveStatus.setText(R.string.chat_activity_livestatus_offline_desc);
				WidgetUtils.setDrawLeft(self(), R.drawable.sns_friend_list_form_item_status_offline_ico2, viewLiveStatus);
			}
		}
	}
	
	/**
	 * 处理Acvitity的回调.
	 * {@inheritDoc}
	 */
	public void onActivityResult(int requestCode, int resultCode, Intent data) {
		super.onActivityResult(requestCode, resultCode, data);

		// 从相册或相机拍照完成后的回调
		if (requestCode == ActivityRequestCode.TAKE_BIG_PICTURE
				|| requestCode == ActivityRequestCode.CHOOSE_BIG_PICTURE2) {
			menuWindowForSendPic.onParantActivityResult(requestCode, resultCode, data);
		}
		// 礼品兑换积分完成后的回调
		else if (requestCode == ActivityRequestCode.GIFTS_RECHARGE_CIONS_RESULT) {
			if (giftsToolsPopupWindow != null)
				giftsToolsPopupWindow.forParentAvtivityResult();
		}
		// 选中要发送的大文件后的回调
		else if (requestCode == ActivityRequestCode.CHOOSE_FILE_RESULT_FROM_ACTIVITY) {
			SendFileHelper.onParantActivityResult(this, requestCode, resultCode, data
					, Const.CHAT_TYPE_FREIDN$CHAT, this.friendUIDForInit, null);
		}
		// 短视频录制完成后的回调
		else if (requestCode == ActivityRequestCode.SHORTVIDEO_RECORD_RESULT_FROM_ACTIVITY) {
			super.onActivityResult_shortVideoRecordComplete(data);
		}
		// "名片"消息时，选择要发送的用户或群的回调
		else if (requestCode == ActivityRequestCode.REQUEST_CODE_FOR_CONTACT_TARGET_CHOOSE) {
			super.onActivityResult_contactTargetSelectedComplete(this, listAdapter, data);
		}
		// "位置"消息时，选择要发送的位置的回调
		else if (requestCode == ActivityRequestCode.REQUEST_CODE_FOR_GETTION) {
			super.onActivityResult_locationSelectedComplete(this, data);
		}
		// 消息转发时，选择转发接收者（用户或群）后的回调
		else if (requestCode == ActivityRequestCode.REQUEST_CODE_FOR_FORWARD_TARGET_CHOOSE) {
			super.onActivityResult_forwardTargetSelectedComplete(this, listAdapter, data);
		}
		// 进入"聊天信息"界面后，activity的结果回调
		else if (requestCode == ActivityRequestCode.REQUEST_CODE_FOR_CHAT_INFO) {
			// 从"聊天信息"界面中他建群聊成功，就关掉本聊天界面（因为群聊创建成功后自然就是在群聊界面里了，本界面就没有存在意义了）
			if (resultCode == ActivityResultCode.RESULT_CODE_FOR_CREATE_GROUP_SUCESS)
				finish();
		} else {
			// do nothing
		}
	}

	/**
	 * 获得消息输入框引用。
	 *
	 * @return
	 */
	@Override
	protected EditText getMsgEditText() {
		return this.txtMsg;
	}

	/**
	 * 获得聊天界面列表Adapter对象的引用。
	 *
	 * @return
	 */
	@Override
	protected AbstractChattingListAdapter getChattingListAdapter() {
		return this.listAdapter;
	}

	/**
	 * 刷新聊天列表。
	 */
	@Override
	protected void  notifyListDataSetChanged(){
		this.listAdapter.notifyDataSetChanged();
	}

	/**
	 * 进入聊天信息界面。
	 *
	 * @param activity
	 * @param uid
	 * @param nickname
	 */
	public static void gotoChatInfoActivity(Activity activity, String uid, String nickname){
		activity.startActivityForResult(IntentFactory.createChatInfoIntent(activity, uid, nickname), ActivityRequestCode.REQUEST_CODE_FOR_CHAT_INFO);
	}

	/**
	 * 进入实时视频聊天呼叫。
	 */
	public static void gotoRealtimeVideoChatCall(Activity context, String friendUID, String nickname){
		//** 自v10.0开始，已取消在在线状态在判断和要求！
//		if(MyApplication.getInstance2().getIMClientManager().getFriendsListProvider().getFriendInfoByUid2(friendUID).isOnline()) {
			context.startActivity(IntentFactory.createVideoCallOutIntent(context, friendUID));
//		}
//		else {
//			WidgetUtils.showToast(context, ToolKits.i18n(context, R.string.general_lover_offline, nickname), ToastType.WARN);
//		}
	}


	//----------------------------------------------------------------- 以下为内部类

	/**
	 * 自定义好友聊天消息列表的adapter类实现.
	 */
	private class FriendChattingListAdapter extends AbstractChattingListAdapter {

		public FriendChattingListAdapter(Activity context, ListView theListViewBtContact, int _chatType, String _toId) {
			super(context, theListViewBtContact, _chatType, _toId, false);//true
		}

		@Override
		protected ArrayListObservable<Message> getChattingDatas() {
			return chattingDatas;
		}

		/**
		 * 图片或语音留言消息的图片上传到服务端（用于暂存和中转）成功后即发送一条消息给聊天的对方.
		 *
		 * @param msgType              MsgType
		 * @param imageOrVoiceFileName
		 * @param fingerPring
		 */
		protected void sendImageOrVoiceMessageAsyncImpl(int msgType, boolean isOutgoing, String imageOrVoiceFileName, String fingerPring) {
			if (isOutgoing) {
				if (msgType == MsgType.TYPE_IMAGE)
					// 并发送一条图片消息给好友（之前图片已成功上传到服务端罗！）
					MessageHelper.sendImageMessageAsync((Activity) context, friendUIDForInit, imageOrVoiceFileName, fingerPring, null);
				else if (msgType == MsgType.TYPE_VOICE)
					// 并发送一条语音留言消息给好友（之前图片已成功上传到服务端罗！）
					MessageHelper.sendVoiceMessageAsync((Activity) context, friendUIDForInit, imageOrVoiceFileName, fingerPring, null);
			}
		}

		/**
		 * 当列表滑到最后一行时将调用的方法.
		 */
		@Override
		protected void onScrollToBottom() {
			if (unreadMessageBallonWrapper != null)
				// 【当消息列表真滑动到最后一条可见时的处理3】 设置“未读数”提示UI不可见（重置未读数为0）
				unreadMessageBallonWrapper.resetUnreadCount();
		}

		/**
		 * 消息重发实现方法。
		 *
		 * @param entity
		 */
		@Override
		protected void reSendImpl(Message entity) {
			if(entity.isForwardOutgoing()) {
				MessageForwardHelper.forward((Activity) context, entity, super.theToId, null);
			} else {
				MessageReSendHelper.reSend((Activity) context, entity, super.theToId);
			}
		}

		/**
		 * 点击实时音视频记录消息的实现方法。
		 *
		 * @param vrm
		 * @since 10.0
		 */
		@Override
		protected void onClickVoipRecordImpl(VoipRecordMeta vrm) {
			if(vrm.getVoipType() == VoipRecordMeta.VOIP_TYPE_VOICE) {
				doRealtimeVoiceChat_checkPermision();
			} else {
				doRealtimeVideoChat_checkPermision();
			}
		}
	}
}
